跳到主要内容

18.样式篇 _ Tailwind CSS、CSS-in-JS 与 Sass

前言

Next.js 支持多种方式添加样式:

  1. 内联 CSS
  2. CSS 模块:创建局部 CSS 避免命名冲突,提升可维护性
  3. 全局 CSS
  4. 外部样式表
  5. Tailwind CSS:一个 CSS 框架,通过组合的方式声明样式
  6. CSS-in-JS:将 CSS 直接嵌入到 JavaScript 组件中,实现动态和局部样式
  7. Sass:最流行的 CSS 预处理器

让我们来一一讲解。

1. 内联 CSS

最基础的添加样式的方式便是使用内联 CSS,举个例子:

// app/about/page.js
export default function About() {
return (
<h1 style={{
color: red;
}}>Hello About!</h1>
)
}

2. CSS 模块

Next.js 内置了对 CSS 模块的支持。使用 CSS 模块,你只需要使用 .module.css作为文件后缀名,Next.js 就会自动进行处理。

CSS 模块的作用在于实现局部 CSS,本质是创建一个不会重复的类名。这样你就可以在不同的文件里使用相同的类名,而不用担心发生样式冲突。这是最理想的实现组件级别 CSS 的方式。

让我们举个例子:

首先,创建一个 styles.module.css文件,样式书写方式如同正常的 CSS 文件:

// app/dashboard/styles.module.css
.dashboard {
padding: 24px;
}

然后,CSS 模块可以被导入到 app 目录下的任意文件,让我们导入并使用该样式:

// app/dashboard/layout.js
import styles from './styles.module.css'

export default function DashboardLayout({ children }) {
return <section className={styles.dashboard}>{children}</section>
}

3. 全局样式

全局样式,顾名思义,应用到所有路由的样式,像我们传统写页面 CSS 的时候,都会引入 normalize.css 或者 reset.css等,这种场景就适合使用全局样式。

全局样式可以被导入 app 目录下的任意 layout、page 或者组件中。(为什么总是强调任意呢?因为在 pages 目录下,也就是之前的 Pages Router 模式,全局样式只能被导入到 _app.js 文件,这是一个新的改变。)

具体怎么使用呢?让我们举个例子:

首先,创建一个 app/global.css 样式文件:

body {
padding: 20px 20px 60px;
max-width: 680px;
margin: 0 auto;
}

然后,在根布局(app/layout.js)导入 global.css,该样式会被应用于应用里的每个路由:

// app/layout.js
import './global.css'

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

4. 外部样式表

你也可以通过导入外部包的方式添加样式,举个例子:

// app/layout.js
import 'bootstrap/dist/css/bootstrap.css'

export default function RootLayout({ children }) {
return (
<html lang="en">
<body className="container">{children}</body>
</html>
)
}

不过要注意,该外部包必须是从 npm 包直接导入或者下载完和你的代码放在一起。

如果我想引用外部 CDN CSS 文件呢?就比如https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css 这种文件?

那你可以使用 <link> 标签添加在 Root Layout 中,就像这样:

// app/layout.js
import "./globals.css";

export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>{ children }</body>
</html>
);
}

使用样式的时候要注意:

即时刷新: 在本地使用next dev 运行项目的时候,本地样式(无论是全局样式还是 CSS 模块),都会在你保存更改后立刻刷新,你可以即时看到样式变化。

打包构建 :当使用 next build 的时候,CSS 文件会被打包成更少的压缩 .css 文件,这是为了减少网络请求,从而提高加载速度,所以不用担心创建多个 css 文件而影响了性能。

禁用 JS: 当你禁用 JavaScript 的时候,在生产版本(next start),样式依然会被加载。也就是说,打包构建后的代码中的 CSS 并不是通过 JS 注入的。但是开发的时候(next dev),为了开启快速刷新,JavaScript 依然是有必要的。

5. Tailwind CSS

Tailwind CSS 是一个非常知名的 CSS 框架,本质是一个工具集,包含了大量比如 flexpt-4text-centerrotate-90等工具类,可以组合使用并直接在 HTML 代码上实现任何 UI 设计。与 Next.js 搭配使用非常顺手。Next.js 官方便是用的 Tailwind CSS。

在使用 create-next-app创建项目的时候,如果你在命令行中选择了使用 Tailwind CSS,则相关配置都会自动生成,可以直接使用。如果没有选择,希望引入 Tailwind CSS,可以参考此步骤。其实 Tailwind CSS 官方也提供了针对各个框架的使用指南:

5.1. 安装

在项目根目录执行以下命令:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

会同时生成 tailwind.config.jspostcss.config.js文件。

5.2. 配置 Tailwind

postcss.config.js 不需要修改。在 tailwind.config.js 中添加使用 Tailwind CSS 类名的文件路径:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}',
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',

// 嫌麻烦,你也可以直接使用 `src` 目录
'./src/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {},
},
plugins: [],
}

5.3. 导入样式

添加 Tailwind CSS 指令,将 Tailwind 的样式注入到全局样式中。使用方式如下:

// app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;

在根布局(app/layout.tsx),导入 globals.css

// app/layout.js 
import './globals.css'

export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

5.4. 使用类名

然后你就可以在应用里使用 Tailwind 的工具类名:

// app/page.js
export default function Page() {
return <h1 className="text-3xl font-bold underline">Hello, Next.js!</h1>
}

5.5. 辅助使用

在 VSCode 中使用的时候,可以安装 Tailwind CSS IntelliSense 这个插件,提供自动功能、语法校验、悬停预览等功能。

image.png

Tailwind CSS 中的工具类众多,记不清的时候也可以查询这个速查表

6. CSS-in-JS

6.1. 介绍

CSS-in-JS,顾名思义,将 CSS 写在 JS 文件里,而不是单独新建如 .css.scss等文件。这样就可以在 CSS 中使用 JS 的变量定义、函数调用、条件判断等功能。

之所以能够流行,也跟 React、Vue 等框架的流行有关,“组件”的概念开始深入人心。因为 Vue 本身有自己的 CSS 方案,React 没有,所以 CSS-in-JS 也多在 React 社区中讨论。

image.png

实现 CSS-in-JS 的库有很多,每个库的实现、使用方式、语法也不尽相同。目前 Next.js 客户端组件中支持使用的库有:

emotion 正在支持中……

如果你希望设置服务端组件的样式,推荐使用 CSS 模块或者其他输出 CSS 文件的解决方案比如 PostCSS 或者 Tailwind CSS。

6.2. 配置方式

在 Next.js 中配置 CSS-in-JS 的基本原理分为三步:

  1. 在渲染的时候有一个包含所有 CSS 规则的样式注册表
  2. 使用 useServerInsertedHTMLhook 在内容被使用前注入样式规则
  3. 使用包含样式注册表的客户端组件包裹应用

6.3. styled-jsx

这三步听起来有些复杂,让我们以 styled-jsx 为例进行讲解。注意在客户端组件使用 styled-jsx至少需要使用 v5.1.0版本。

首先创建一个新的注册表:

'use client'
// app/registry.js
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'

export default function StyledJsxRegistry({ children }) {
const [jsxStyleRegistry] = useState(() => createStyleRegistry())

useServerInsertedHTML(() => {
const styles = jsxStyleRegistry.styles()
jsxStyleRegistry.flush()
return <>{styles}</>
})

return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
}

然后用此包含注册表的组件包裹根组件的 children

// app/layout.js
import StyledJsxRegistry from './registry'

export default function RootLayout({ children }) {
return (
<html>
<body>
<StyledJsxRegistry>{children}</StyledJsxRegistry>
</body>
</html>
)
}

然后你就可以在 page.js 中使用:

export default function Page() {
return (
<div>
<div className="container">text
</div>

<style jsx>{`
.container {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 1.5rem /* 24px */;
}

@media (min-width: 1024px) {
.container {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
`}</style>
</div>
);
}

示例代码地址:https://github.com/vercel/app-playground/tree/main/app/styling/styled-jsx

6.4. Styled Components

Styled Components 的配置也大致如此,注意使用 styled-components@6 或者更高版本。

首先,创建一个全局注册表:

'use client'
// lib/registry.js
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

export default function StyledComponentsRegistry({ children }) {
const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())

useServerInsertedHTML(() => {
const styles = styledComponentsStyleSheet.getStyleElement()
styledComponentsStyleSheet.instance.clearTag()
return <>{styles}</>
})

if (typeof window !== 'undefined') return <>{children}</>

return (
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
{children}
</StyleSheetManager>
)
}

然后用此包含注册表的组件包裹根组件的 children

// app/layout.js
import StyledComponentsRegistry from './lib/registry'

export default function RootLayout({ children }) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
)
}

然后你就可以在 page.js 中使用:

'use client';

import styled from 'styled-components';

const Container = styled.div`
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1.5rem /* 24px */;
`;

const SkeletonInner = styled.div`
padding: 1rem /* 16px */;
background-color: rgb(24 24 27 / 0.8);
border-radius: 1rem /* 16px */;
`;

const SkeletonImg = styled.div`
height: 3.5rem /* 56px */;
border-radius: 0.5rem /* 8px */;
background-color: rgb(63 63 70 / 1);
`;

const SkeletonBtn = styled.div`
margin-top: 0.75rem /* 12px */;
width: 25%;
height: 0.75rem /* 12px */;
border-radius: 0.5rem /* 8px */;
background-color: rgb(255 0 128 / 1);
`;

const Skeleton = () => (
<SkeletonInner>
<SkeletonImg />
<SkeletonBtn />
</SkeletonInner>
);

export default function Page() {
return (
<div className="space-y-4">
<h1 className="text-xl font-medium text-gray-400/80">
Styled with Styled Components
</h1>
<Container>
<Skeleton />
<Skeleton />
<Skeleton />
</Container>
</div>
);
}

示例代码地址:https://github.com/vercel/app-playground/tree/main/app/styling/styled-components

7. Sass

7.1. Sass 使用

Sass 作为知名的 CSS 预处理器已无须过多介绍。Next.js 内置了对 Sass 文件的支持,你需要使用 .scss.sass作为文件后缀。

你也可以结合 CSS 模块使用组件级别的 Sass, 你需要使用.module.scss或者 .module.sass作为文件后缀。

使用 sass,你需要首先安装 sass

npm install --save-dev sass

7.2. 自定义配置

如果你希望配置 Sass 编译器,使用 next.config.jssassOptions选项:

// next.config.js
const path = require('path')

module.exports = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},
}

7.3 Sass 变量

Next.js 支持从 CSS 模块文件导出 Sass 变量。这是一个示例代码:

// app/variables.module.scss
$primary-color: #64ff00;

:export {
primaryColor: $primary-color;
}
// app/page.js
// maps to root `/` URL

import variables from './variables.module.scss'

export default function Page() {
return <h1 style={{ color: variables.primaryColor }}>Hello, Next.js!</h1>
}

参考链接

  1. Styling: CSS Modules
  2. Styling: Tailwind CSS
  3. Styling: CSS-in-JS
  4. Styling: Sass
  5. State of CSS 2023: CSS-in-JS